(function() {
	var Cc = Components.classes;
	var Ci = Components.interfaces;
	var Cu = Components.utils;
	var MAX_TAB_NAME_LEN = 30;
	var $$ = {
		INPUT_APP: '#input-target-app',
		BUTTON_LOAD: '#button-loadapp',
		BUTTON_APPLY: '#button-apply',
		APP_CONTAINER: '#app-container iframe',
		APP_IFRAME: '#app-iframe',
		INPUT_TARGET: '#input-target-object',
		INPUT_ATTR: '#input-target-attr'
	};
	var apps = null;
	var targetApp = null;
	var targetObjectString = null;
	var com = getBinaryComponent();
	var targetObjects = [];
	var targetAttributes = [];
	var resolving = false;

	function getCallStack() {
		try {
			i.dont.exist += 0;
		} catch(ex) {
			var stack = [];
			var lines = ex.stack.split('\n');
			for (var i = 2, len = lines.length; i < len; i++) {
				/*
				if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
					stack.push(lines[i]);
				}
				*/
				stack.push(lines[i]);
			}
			return stack;
		}
	}
	var attrMembrane = {
		convert: function(wrappee, wrapped, type) {
			// alert('convert');
			return wrappee.valueOf();
		},
		resolve: function(wrappee, wrapped, attr) {
			if (wrappee[attr] !== undefined) {
				resolving = true;
				wrapped[attr] = wrappee[attr];
				resolving = false;
				return wrapped;
			} else {
				return undefined;
			}
		},
		enumerate: function(wrappee, wrapped) {
			return Iterator(wrappee, true);
		},
		iteratorObject: function(wrappee, wrapped, keysOnly) {
			return Iterator(wrappee, keysOnly);
		},
		getProperty: function(wrappee, wrapped, attr, def) {
			return def;
		},
		setProperty: function(wrappee, wrapped, attr, value) {
			if (!resolving) {
				try {
					var pos = targetObjects.indexOf(wrappee);
					if (pos > - 1) {
						if (attr in targetAttributes[pos]) {
							if (targetAttributes[pos][attr].onSet) {
								if (targetAttributes[pos][attr].onSet(wrappee[attr], value, getCallStack()) === false) {
									return wrappee[attr];
								}
							} else {
								// NOTE: default callback;
								alert(attr + ' is set to ' + value + '.\n(You can overwrite this default callback by adding "onSet" property in the forth parameter when setup observer.)');
								alert('call stack: \n' + getCallStack());
							}
						}
					}
				} catch(ex) {
					alert(ex);
				}
				wrappee[attr] = value;
			}
			return value;
		},
		delProperty: function(wrappee, wrapped, attr) {
			try {
				var pos = targetObjects.indexOf(wrappee);
				if (pos > - 1) {
					if (attr in targetAttributes[pos]) {
						if (targetAttributes[pos][attr].onDelete) {
							if (targetAttributes[pos][attr].onDelete(wrappee[attr], getCallStack()) === false) {
								return false;
							}
						} else {
							// NOTE: default callback;
							alert(attr + ' is deleted. \n(You can overwrite this default callback by adding "onSet" property in the forth parameter when setup observer.)');
							alert('call stack: \n' + getCallStack());
						}
					}
				}
			} catch(ex) {
				alert(ex);
			}
			delete(wrappee[attr]);
			return true;
		}
	};
	function getBinaryComponent() {
		try {
			var factory = Cc["@labs.mozilla.com/jetpackdi;1"].createInstance(Ci.nsIJetpack);
			return factory.get();
		} catch(e) {
			return null;
		}
	}
	function loadApp(href) {
		$($$.APP_IFRAME).attr('src', href);
	}
	function __observe__(getter, setter, targetAttribute, handlers) {
		try {
			var targetObject = getter();
			var pos = targetObjects.indexOf(targetObject);
			if (pos == -1) {
				setter(com.wrap(targetObject, attrMembrane));
				pos = targetObjects.push(targetObject) - 1;
				targetAttributes[pos] = {};
			}
			targetAttributes[pos][targetAttribute] = (handlers || {});
		} catch(ex) {
			alert(ex);
		}
	}
	function __unobserve__(getter, setter, targetAttribute) {
		try {
			var wrappee = com.unwrap(getter());
			if (wrappee) {
				var pos = targetObjects.indexOf(wrappee);
				if (pos > - 1) {
					delete(targetAttributes[pos][targetAttribute]);
				}
			}
		} catch(ex) {
			alert(ex);
		}
	}
	function setObserver(app) {
		app.__observe__ = __observe__;
		app.__unobserve__ = __unobserve__;
	}
	function regEvents() {
		$($$.APP_IFRAME).load(function(evt) {
			try {
				targetApp = document.getElementById('app-iframe').contentWindow.wrappedJSObject;
				if (targetApp) {
					setObserver(targetApp);
				}
				targetObjects = [];
				targetAttributes = [];
			} catch(ex) {
				alert(ex);
			}
		});
		$($$.SEL_APPS).change(function(evt) {
			try {} catch(ex) {
				alert(ex);
			}
		});
		$($$.BUTTON_LOAD).click(function(evt) {
			try {
				loadApp($($$.INPUT_APP).val());
			} catch(ex) {
				alert(ex);
			}
		});
		$($$.BUTTON_APPLY).click(function(evt) {
			try {
				var targetObjectString = $($$.INPUT_TARGET).val();
				var targetAttributeString = $($$.INPUT_ATTR).val();
				try {
					// TODO: update this for complex object string;
					// NOTE: now select only .;
					var targetWindow = targetApp;
					var path = targetObjectString.split('.');
					var targetObject = targetWindow;
					var parentObject = null;
					var attr = null;
					do {
						parentObject = targetObject;
						attr = path.shift();
						targetObject = targetObject[attr];
					} while (path.length);
					var membrane = {
						"call": function(wrappee, wrappedObject, $, args) {
							throw 'call';
							return false;
						},
						"resolve": function(wrappee, wrappedObject, name) {
							if (name == 'toString') {
								wrappedObject.toString = function() {
									return '(wrapped)';
								};
								return wrappedObject;
							}
							if (wrappee[name] !== undefined) {
								wrappedObject[name] = wrappee[name];
								return wrappedObject;
							} else {
								return undefined;
							}
						}
					};
					var wrapped = com.wrap(targetObject, membrane);
					// wrapped.toString = function() {return ''};
					parentObject[attr] = wrapped;
				} catch(ex) {
					alert(ex);
					alert('the object is not existed!');
				}
			} catch(ex) {
				alert(ex);
			}
		});
	}
	function onReady() {
		try {
			$($$.APP_IFRAME).attr('src', '');
			regEvents();
		} catch(ex) {
			alert(ex);
		}
	}
	$(window).ready(onReady);
})();

